#include <string.h>
#include <err.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <assert.h>
#include "file_db.h"
#include "bstrlib.h"
#include "server.h"

struct sfiles_db{
    file_db *file; /*Arreglo de archivos*/
    int size; /*Cantidad de archivos*/
};

struct sfile_db{
    char *name; /*Nombre del archivo*/
    char *md5; /*Suma MD5 del archivo*/
    int slices; /*Partes que tiene el archivo*/
    int size; /*Tamaño total*/
    int requested; /*Cuantas partes pedi*/
    bool finished; /*Si tengo el archivo completo*/
    bool index; /*Ya le informe al tracker de este archivo?*/
    part_db *parts; /*Arreglo de partes (cantidad==slices)*/
};

struct spart_db{
    int num; /*Numero de la parte*/
    int num_servers; /*Cuantos servidores poseen esta parte*/
    int fd; /*fd del servidor desde donde estoy bajando*/
    int queued; /*Cantidad de servidores encolados*/
    bool finished; /*tengo el pedazo completo?*/
    bool sent; /*ya envie este fragmento?*/
    char *md5; /*MD5 de la parte*/
    char **servers; /*Arreglo de los servidores que tienen la parte*/
    char **queue; /*Arreglo de servidores donde me encole esperando un hueco*/
};

/*
    Constructor del tipo files_db
*/
files_db
files_create(void){
    struct sfiles_db *files;
    files = calloc(1, sizeof(struct sfiles_db));
    files->size = 0;
    #ifdef __DEBUG__
        printf("Creando un file\n");
    #endif
    files->file = calloc(1, sizeof(file_db*));
    return files;
}

/*
    Destructor del tipo files_db
*/
void
files_destroy(files_db files){
    int i=0;
    for (i=0; i<files->size;i++){
        file_db_destroy(files->file[i]);
        files->file[i]=NULL;
    }
    free(files->file);
    files->file=NULL;
    free(files);
    files=NULL;
}

/*
    Meto un file en la lista
*/
void
files_add_file(files_db files, file_db file){
    if(files->size>0){
        files->file = realloc(files->file, sizeof(file_db*) *((files->size)+1));
    }
    files->file[files->size] = file;
    files->size = files->size + 1;
}

/*
    Pido la cantidad de archivos
*/
int
files_get_number_of_files(files_db files){
    return files->size;
}

/*
    Constructor del tipo part_db
*/
part_db
part_db_create(int i){
    struct spart_db *part;
    part = calloc(1, sizeof(struct spart_db));
    part->num = i;
    part->num_servers = 0;
    part->fd = -1;
    part->queued = 0;
    part->finished = false;
    part->sent = false;
    part->md5 = calloc(34, sizeof(char));
    part->servers = calloc(1, sizeof(char*));
    part->queue = calloc(1, sizeof(char*));
    return part;
}

/*
    Destructor del tipo part_db
*/
void
part_db_destroy(part_db part){
    int i=0;
    if (part->md5 != NULL){
        free(part->md5);
        part->md5 = NULL;
    }
    for (i=0;i<part_db_get_num_servers(part);i++){
        free(part->servers[i]);
        part->servers[i]=NULL;
    }
    free(part->servers);
    part->servers = NULL;
    for (i=0;i<part_db_queue_size(part);i++){
        free(part->queue[i]);
        part->queue[i]=NULL;
    }
    free(part->queue);
    part->queue = NULL;
    free(part);
    part = NULL;
}

/*
    Pido el numero de parte a la que corresponde este pedazo
*/
int
part_db_get_num(part_db part){
    return part->num;
}

/*
    Indico cuantos servidores tienen la partes
*/
void
part_db_set_num_servers(part_db part, int num){
    part->num_servers=num;
}

/*
    Pido cuantos servidores tienen la partes
*/
int
part_db_get_num_servers(part_db part){
    return part->num_servers;
}

/*
    Pido el fd del server de donde estoy bajando el pedazo
*/
int
part_db_get_fd(part_db part){
    return part->fd;
}

/*
    Indico el fd del server de donde estoy bajando
*/
void
part_db_set_fd(part_db part, int fd){
    part->fd = fd;
}

/*
    Pido el numero de items que tengo encolados
*/
int
part_db_queue_size(part_db part){
    return part->queued;
}

/*
    Seteo el numero de items que tengo encolados
*/
void
part_db_set_queue_size(part_db part, int size){
    part->queued=size;
}


/*
    Indico que tengo el pedazo completo
*/
void
part_db_completed(part_db part){
    part->finished=true;
}

/*
    Indico que no tengo el pedazo completo
*/
void
part_db_not_completed(part_db part){
    part->finished=false;
}

/*
    Pregunto si tengo el pedazo completo
*/
bool
part_db_is_complete(part_db part){
    return part->finished;
}


/*
    Indico que ya envie este pedazo
*/
void
part_db_sent(part_db part){
    part->sent=true;
}

/*
    Indico que no envie el pedazo
*/
void
part_db_not_sent(part_db part){
    part->sent=false;
}

/*
    Pregunto si envie el pedazo
*/
bool
part_db_is_sent(part_db part){
    return part->sent;
}

/*
    Seteo el MD5
*/
void
part_db_set_md5(part_db part, char *md5){
    strncpy(part->md5, md5, strlen(md5));
}

/*
    Pido el MD5
    IMPORTANTE: NO LIBERAR EL CHAR DEVUELTO YA QUE NO ES UNA COPIA
*/
char *
part_db_get_md5(part_db part){
    return part->md5;
}

/*
    Agrego un servidor
*/
void
part_db_add_server(part_db part, char *server){
    int cant=0;
    cant = part_db_get_num_servers(part);
    part->servers = realloc(part->servers,sizeof(char*) * (cant+1));
    part->servers[cant] = calloc(strlen(server), sizeof(char));
    strncpy(part->servers[cant], server, strlen(server));
    part_db_set_num_servers(part, cant+1);
}

/*
    Pido el primer servidor
    NULL en caso de no haber ninguno
*/
char *
part_db_get_server(part_db part){
    char *server=NULL;
    if(part_db_get_num_servers(part)==0){
        return NULL;
    }
    server = calloc(strlen(part->servers[0]),sizeof(char));
    strncpy(server, part->servers[0], strlen(part->servers[0]));
    return server;
}

/*
    Elimino el primer servidor
*/
void
part_db_remove_server(part_db part){
    int i=0, cant=0;
    cant = part_db_get_num_servers(part);
    if (cant>0){
        free(part->servers[0]);
        for (i=0;i<cant-1;i++){
            part->servers[i] = part->servers[i+1];
        }
        if(cant==1){
            free(part->servers);
            part->servers = calloc(1, sizeof(char*));
        }
        else{
            part->servers = realloc(part->servers, sizeof(char*)*(cant-1));
        }
        part_db_set_num_servers(part, cant-1);
    }
}

/*
    Llevo el primer servidor al fondo y muevo todos para adelante
*/
void
part_db_swap_servers(part_db part){
    int i=0, cant=0;
    char *aux=NULL;
    aux = part->servers[0];
    cant = part_db_get_num_servers(part);
    for (i=0;i<cant-1;i++){
        part->servers[i] = part->servers[i+1];
    }
    part->servers[cant-1] = aux;
}

/*
    Encolo el primer servidor
*/
void
part_db_enqueue(part_db part){
    int cant=0;
    cant = part_db_queue_size(part);
    part->queue = realloc(part->queue, sizeof(char*)*(cant+1));
    part->queue[cant] = calloc(strlen(part->servers[0]), sizeof(char));
    strncpy(part->queue[cant], part->servers[0], strlen(part->servers[0]));
    part_db_set_queue_size(part, cant+1);
}

/*
    Pido el servidor en la posicion i de la cola
    IMPORTANTE: NO LIBERAR LO DEVUELTO YA QUE NO ES UNA COPIA
*/
char *
part_db_get_server_from_queue(part_db part, int i){
    return part->queue[i];
}

/*
    Elimino el servidor ubicado en la posicion i de la cola
*/
void
part_db_remove_from_queue(part_db part, int i){
    int j=0, cant=0;
    cant = part_db_queue_size(part);
    if (cant>i){
        free(part->queue[i]);
        for (j=i;j<cant-1;j++){
            part->queue[j] = part->queue[j+1];
        }
        if(cant==1){
            free(part->queue);
            part->queue = calloc(1, sizeof(char*));
        }
        else{
            part->queue = realloc(part->queue, sizeof(char*)*(cant-1));
        }
        part_db_set_queue_size(part, cant-1);
    }
}

/*
    Constructor del tipo file_db
*/
file_db
file_db_create(char *name, char *md5, int slices, int size){
    int i=0;
    struct sfile_db *file;
    file = calloc(1, sizeof(struct sfile_db));
    file->name = calloc(strlen(name), sizeof(char));
    strncpy(file->name, name, strlen(name));
    file->md5 = calloc(strlen(md5), sizeof(char));
    strncpy(file->md5, md5, strlen(md5));
    file->slices = slices;
    file->requested = 0;
    file->size = size;
    file->finished = false;
    file->index = false;
    file->parts = calloc(slices, sizeof(struct spart_db));
    for (i=0;i<file->slices;i++){
        file->parts[i] = part_db_create(i+1);
    }
    return file;
}

/*
    Destructor del tipo file_db
*/
void
file_db_destroy(file_db file){
    if (file->name != NULL){
        free(file->name);
        file->name = NULL;
    }
    if (file->md5 != NULL){
        free(file->md5);
        file->md5 = NULL;
    }
    if (file->parts != NULL){
        while(file_db_get_slices(file)>0){
            part_db_destroy(file->parts[file_db_get_slices(file)-1]);
            file->parts[file_db_get_slices(file)-1]=NULL;
            /*Dirty Hack*/
            file->slices = file->slices - 1;
        }
        free(file->parts);
        file->parts = NULL;
    }
    free(file);
    file = NULL;
}

/*
    Funcion para obtener el nombre del archivo
*/
char *
file_db_get_name(file_db file){
    char *aux=NULL;
    aux = calloc(strlen(file->name), sizeof(char));
    strncpy(aux, file->name, strlen(file->name));
    return aux;
}

/*
    Funcion para obtener el md5 del archivo
*/
char *
file_db_get_md5(file_db file){
    char *aux=NULL;
    aux = calloc(strlen(file->md5), sizeof(char));
    strncpy(aux, file->md5, strlen(file->md5));
    return aux;
}

/*
    Funcion que me devuelve cuantas partes tiene el archivo
*/
int
file_db_get_slices(file_db file){
    return file->slices;
}

/*
    Funcion que me devuelve el tamaño del archivo en bytes
*/
int
file_db_get_size(file_db file){
    return file->size;
}

/*
    Marco cuantas partes del archivo ya he pedido
*/
void
file_db_set_requested(file_db file, int requested){
    file->requested = requested;
}

/*
    Pregunto cuantas partes ya he pedido
*/
int
file_db_get_requested(file_db file){
    return file->requested;
}

/*
    Indico que tengo el archivo completo
*/
void
file_db_finished(file_db file){
    file->finished = true;
}

/*
    Indico que no tengo el archivo completo
*/
void
file_db_not_finished(file_db file){
    file->finished = false;
}

/*
    Pregunto si tengo el archivo completo
*/
bool
file_db_has_finished(file_db file){
    return file->finished;
}

/*
    Indico que ya le informé al tracker sobre este archivo
*/
void
file_db_index_informed(file_db file){
    file->index = true;
}

/*
    Indico que el tracker todavia no conoce este archivo
*/
void
file_db_index_not_informed(file_db file){
    file->index = false;
}

/*
    Pregunto si le he informado al tracker sobre este archivo
*/
bool
file_db_is_index_informed(file_db file){
    return file->index;
}

/*
    Funcion para obtener la parte i
    IMPORTANTE: NO DESTRUIR EL ITEM, YA QUE NO ES UNA COPIA
*/
part_db
file_db_get_part(file_db file, int i){
    return file->parts[i-1];
}
